import path from 'path'
import JSON5 from 'json5'
import YAML from 'yaml'
import * as fs from 'fs-extra'
import esm from 'esm'
import { Options, resolveOptions } from './options'
import { set } from 'lodash-es'

const _require = esm(module)

const parsers = new Map<string, (file: string) => any>([
  ['', (file) => parseDotEnvFile(fs.readFileSync(file).toString())],
  ['.js', (file) => _require(path.resolve(file))],
  ['.json', (file) => JSON5.parse(fs.readFileSync(file).toString())],
  ['.yaml', (file) => YAML.parse(fs.readFileSync(file).toString())],
  ['.mjs', (file) => _require(path.resolve(file))],
  ['.cjs', (file) => _require(path.resolve(file))],
  ['.es', (file) => _require(path.resolve(file))],
  ['.json5', (file) => JSON5.parse(fs.readFileSync(file).toString())],
  ['.yml', (file) => YAML.parse(fs.readFileSync(file).toString())],
])

function _parse(prefix: string) {
  for (const [ext, parser] of parsers) {
    const file = prefix + ext
    if (fs.existsSync(file)) {
      try {
        return parser(file)
      } catch {}
    }
  }
  return {}
}

export function parse(options: Options = {}) {
  const opts = resolveOptions(options)
  const obj: Record<string, any> = _parse(opts.prefix)
  if (opts.enabledMode) {
    Object.assign(obj, _parse(opts.prefix + '.' + opts.mode))
  }
  return obj
}

const NEWLINE = '\n'
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/
const RE_NEWLINES = /\\n/g
const NEWLINES_MATCH = /\r\n|\n|\r/

function parseDotEnvFile(src: string) {
  const obj: any = {}

  // convert Buffers before splitting into lines and processing
  src.split(NEWLINES_MATCH).forEach(function (line, idx) {
    // matching "KEY' and 'VAL' in 'KEY=VAL'
    const keyValueArr = line.match(RE_INI_KEY_VAL)
    // matched?
    if (keyValueArr != null) {
      const key = keyValueArr[1]
      // default undefined or missing values to empty string
      let val = keyValueArr[2] || ''
      const end = val.length - 1
      const isDoubleQuoted = val[0] === '"' && val[end] === '"'
      const isSingleQuoted = val[0] === "'" && val[end] === "'"

      // if single or double quoted, remove quotes
      if (isSingleQuoted || isDoubleQuoted) {
        val = val.substring(1, end)

        // if double quoted, expand newlines
        if (isDoubleQuoted) {
          val = val.replace(RE_NEWLINES, NEWLINE)
        }
      } else {
        // remove surrounding whitespace
        val = val.trim()
      }

      set(obj, key, val)
    } else {
      console.warn(`did not match key and value when parsing line ${idx + 1}: ${line}`)
    }
  })

  return obj
}
